﻿using System;

namespace GKS.DataStructures {
    /// <summary>
    /// A class useful in various pattern matching algorithms.
    /// </summary>
    /// <remarks>
    /// Next steps:
    ///     - Improve Exception management.
    ///     - Insert Logging instructions.
    /// </remarks>
    public class ZBox {
        #region variables
        // the start index of the zbox.
        private int startIndex = -1;

        // the size of the zbox.
        private int size = 0;
        #endregion

        #region properties
        /// <summary>
        /// Get or Set the start index of the ZBox.
        /// </summary>
        /// <remarks>
        /// The empty ZBox has a start index equal to -1 and a size equal to zero.
        /// </remarks>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown if the new value is smaller than -1.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// Thrown if the new value is -1 and the size is greater than zero.
        /// </exception>
        public int StartIndex {
            get {
                return startIndex;
            }
            set {
                if (value < -1) {
                    throw new ArgumentOutOfRangeException(
                        "value", 
                        "The start index must be greater than or equal to -1.");
                }

                if (size > 0 && value == -1) {
                    throw new ArgumentException("The start index cannot be set to -1 while the size is zero.");
                }

                startIndex = value;
            }
        }

        /// <summary>
        /// Get the end index of the ZBox.
        /// </summary>
        /// <remarks>
        /// This index is included in the ZBox. For example, a ZBox starting at 
        /// position 1 with size 5 will have an EndIndex equal to 5.
        /// </remarks>
        public int EndIndex {
            get {
                return startIndex == -1 
                        ? -1                        // the size must be zero for this to happen
                        : size - startIndex + 1;
            }
        }

        /// <summary>
        /// Get or Set the size of the ZBox.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown if the new value is smaller than zero.
        /// </exception>
        /// <remarks>
        /// The first time the size is set to a value greater than zero, if the start index is 
        /// equal to -1 it is set automatically to zero.
        /// </remarks>
        public int Size {
            get {
                return size;
            }
            set {
                if (value < 0) {
                    throw new ArgumentOutOfRangeException("value", "The size of the ZBox must be greater than or equal to zero.");
                }

                if (size == 0 && startIndex == -1) {
                    startIndex = 0;
                }

                size = value;
            }
        }
        #endregion

        #region constructors
        /// <summary>
        /// A constructor of a ZBox.
        /// </summary>
        public ZBox() : this(-1, 0) { }

        /// <summary>
        /// A constructor of a ZBox.
        /// </summary>
        /// <param name="startIndex">
        /// The starting index of the ZBox.
        /// </param>
        /// <param name="size">
        /// The size of the ZBox.
        /// </param>
        public ZBox(int startIndex, int size) {
            StartIndex = startIndex;
            Size = size;
        }
        #endregion

        #region methods
        /// <summary>
        /// Get a clone of this ZBox.
        /// </summary>
        /// <returns>
        /// A clone of this ZBox.
        /// </returns>
        public ZBox Clone() {
            return new ZBox(StartIndex, Size);
        }

        /// <summary>
        /// Check if this ZBox is equal to a specified object.
        /// </summary>
        /// <param name="obj">
        /// The object to check for equality.
        /// </param>
        /// <returns>
        /// TRUE if specified object is equal to this ZBox,
        /// FALSE otherwise.
        /// </returns>
        public override bool Equals(object obj) {
            return (obj is ZBox) && Equals((ZBox)obj);
        }

        /// <summary>
        /// Check if this ZBox is equal to another ZBox.
        /// </summary>
        /// <param name="other">
        /// The other ZBox to check for equality.
        /// </param>
        /// <returns>
        /// TRUE if specified ZBox is equal to this ZBox,
        /// FALSE otherwise.
        /// </returns>
        public bool Equals(ZBox other) {
            return this.Size == other.Size && this.StartIndex == other.StartIndex;
        }

        /// <summary>
        /// Get the hash code of this ZBox.
        /// </summary>
        /// <returns>
        /// The hash code of this ZBox.
        /// </returns>
        public override int GetHashCode() {
            return StartIndex.GetHashCode() ^ Size.GetHashCode() ^ EndIndex.GetHashCode();
        }

        /// <summary>
        /// Get a string representation of this ZBox.
        /// </summary>
        /// <returns>
        /// A string representation of this ZBox.
        /// </returns>
        public override string ToString() {
            return string.Format(
                "{0}\n{1}\n{2}",
                string.Format("Start Index:\t{0}", StartIndex),
                string.Format("End Index:\t\t{0}", EndIndex),
                string.Format("Size:\t\t\t{0}", Size));
        }
        #endregion

        #region operators
        /// <summary>
        /// Increment by one the size of the specified ZBox.
        /// </summary>
        /// <param name="zbox">
        /// The ZBox whose size must be incremented.
        /// </param>
        public static ZBox operator ++(ZBox zbox) {
            ZBox result = zbox.Clone();
            result.Size++;

            return result;
        }

        /// <summary>
        /// Decrement by one the size of the specified ZBox.
        /// </summary>
        /// <param name="zbox">
        /// The ZBox whose size must be decremented.
        /// </param>
        public static ZBox operator --(ZBox zbox) {
            ZBox result = zbox.Clone();

            try { result.Size--; } 
            catch (Exception e) { /* manage exception here*/ }

            return result;
        }

        /// <summary>
        /// Check if two ZBoxes are equal to one another.
        /// </summary>
        /// <param name="left">
        /// The ZBox on the left side of the operator.
        /// </param>
        /// <param name="right">
        /// The ZBox on the right side of the operator.
        /// </param>
        /// <returns>
        /// TRUE if the two ZBoxes are equal, FALSE
        /// otherwise.
        /// </returns>
        public static bool operator ==(ZBox left, ZBox right) {
            return left.Equals(right);
        }

        /// <summary>
        /// Check if two ZBoxes are different from one another.
        /// </summary>
        /// <param name="left">
        /// The ZBox on the left side of the operator.
        /// </param>
        /// <param name="right">
        /// The ZBox on the right side of the operator.
        /// </param>
        /// <returns>
        /// TRUE if the two ZBoxes are different, FALSE
        /// otherwise.
        /// </returns>
        public static bool operator !=(ZBox left, ZBox right) {
            return !left.Equals(right);
        }
        #endregion
    }
}
